﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// API中间件
    /// </summary>
    public partial class ApiMiddleware : MiddlewareAbs
    {
        /// <summary>
        /// 序列化器
        /// </summary>
        private readonly IWebSerializer _serializer;

        /// <summary>
        /// 固定url的api方法集合[key:http-method;value:[key:url;value:APIMethodInfo]]
        /// </summary>
        private readonly Dictionary<string, Dictionary<string, ApiMethod>> _fixUrlApiMethodDic = new Dictionary<string, Dictionary<string, ApiMethod>>();

        /// <summary>
        /// 模板url的api方法集合[key:http-method;value:请求方法对应的api方法集合]
        /// </summary>
        private readonly Dictionary<string, List<ApiMethod>> _templateUrlApiMethodDic = new Dictionary<string, List<ApiMethod>>();


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="serializer">序列化器</param>
        public ApiMiddleware(IWebSerializer serializer = null)
            : base()
        {
            if (serializer == null)
            {
                serializer = DefaultWebSerializer.Default;

            }

            this._serializer = serializer;
        }

        /// <summary>
        /// 添加api
        /// </summary>
        /// <param name="apiObj">RouteAttribute特性标记类的实例或实现IDynamicApi接口类的实例,没有时以类型名称为路由</param>
        /// <exception cref="ArgumentNullException">api对象为空将抛出此异常</exception>
        public void AddApi(object apiObj)
        {
            if (apiObj == null)
            {
                throw new ArgumentNullException(nameof(apiObj));
            }

            var dynamicApi = apiObj as IDynamicApi;
            if (dynamicApi != null)
            {
                this.AddDynamicApi(dynamicApi);
            }
            else
            {
                this.AddRouteApi(apiObj);
            }
        }

        /// <summary>
        /// 添加动态api
        /// </summary>
        /// <param name="dynamicApi">动态api实例</param>
        /// <exception cref="ArgumentNullException">动态api实例null异常</exception>
        /// <exception cref="NotSupportedException">不支持http方法异常</exception>
        /// <exception cref="ArgumentException">其它参数异常</exception>
        private void AddDynamicApi(IDynamicApi dynamicApi)
        {
            IEnumerable<DynamicApiInfo> apiInfos = dynamicApi.GetWebApiInfos();
            if (apiInfos == null)
            {
                return;
            }

            string basesUrl, urlTemplate;
            HttpMethodAbsAttribute methodAttribute;
            Type type = dynamicApi.GetType();
            MethodInfo methodInfo;

            foreach (DynamicApiInfo apiInfo in apiInfos)
            {
                if (string.IsNullOrWhiteSpace(apiInfo.Template))
                {
                    throw new ArgumentNullException(nameof(apiInfo.Template));
                }

                this.GetBasesUrl(apiInfo.Template, out basesUrl, out urlTemplate);

                switch (apiInfo.HttpRequestMethod.Trim().ToUpper())
                {
                    case HttpRequestMethods.GET:
                        methodAttribute = new HttpGetAttribute(urlTemplate);
                        break;
                    case HttpRequestMethods.POST:
                        methodAttribute = new HttpPostAttribute(urlTemplate);
                        break;
                    case HttpRequestMethods.PUT:
                        methodAttribute = new HttpPutAttribute(urlTemplate);
                        break;
                    case HttpRequestMethods.DELETE:
                        methodAttribute = new HttpDeleteAttribute(urlTemplate);
                        break;
                    case HttpRequestMethods.EXTENSION_MOTHED:
                        methodAttribute = new HttpExtensionMethodAttribute(urlTemplate);
                        break;
                    default:
                        throw new NotSupportedException($"不支持的Http请求方法\"{apiInfo.HttpRequestMethod}\"");
                }

                methodAttribute.SetConverter(apiInfo.Converter);

                methodInfo = apiInfo.ApiMethodInfo;
                if (methodInfo == null)
                {
                    methodInfo = type.GetMethod(apiInfo.ApiMethodName);
                    if (methodInfo == null)
                    {
                        throw new ArgumentException($"类型\"{type.FullName}\"中方法不存在方法\"{apiInfo.ApiMethodName}\"");
                    }
                }

                var apiMethod = new ApiMethod(this._serializer, basesUrl, dynamicApi, methodInfo, methodAttribute);
                //根据url类型当回到相应的集合中
                this.AddApiMethod(apiMethod);
            }
        }


        /// <summary>
        /// 添加静态路由api
        /// </summary>
        /// <param name="obj">RouteAttribute特性标记类的对象,没有时以类型名称为路由</param>
        private void AddRouteApi(object obj)
        {
            /***************************************************************************************
            * [Route("api/v1/gis")]
            * 
            * 请求类型模板示例:(obj => posture)
            * [HttpGet]                              api\v1\posture\markerImage
            * [HttpGet("id={id}")]                   api\v1\posture\markerImage?id={id}.png         \images\markerImage?id=123.png
            * [HttpGet("{z}/{x}/{y}.png")]           api\v1\posture\markerImage\{x}\{y}\{z}.png     \images\markerImage\1\1\23.png
            * [HttpGet("abc{z}/gg{x}mm/kk/{y}.png")]                                                testApi/abc6/gg10mm/kk/50.png
            * [HttpPost("uploadMarkerImage")]
            * [HttpDelete("{fileName}")] 
            ***************************************************************************************/


            Type type = obj.GetType();
            string routTemplate = GetRoutTemplate(type);
            Type httpMethodAbsAttributeAttType = typeof(HttpMethodAbsAttribute);
            MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            object[] objAtts;
            ApiMethod apiMethod;

            foreach (MethodInfo methodInfo in methodInfos)
            {
                objAtts = methodInfo.GetCustomAttributes(httpMethodAbsAttributeAttType, true);
                if (objAtts == null || objAtts.Length == 0)
                {
                    //未标注api特性的方法,忽略
                    continue;
                }

                //创建api方法
                apiMethod = new ApiMethod(this._serializer, routTemplate, obj, methodInfo, (HttpMethodAbsAttribute)objAtts[0]);

                //根据url类型当回到相应的集合中
                this.AddApiMethod(apiMethod);
            }
        }

        private void AddApiMethod(ApiMethod apiMethod)
        {
            ApiMethod oldApiMethod;
            string routUrl;
            Dictionary<string, ApiMethod> apiMethodDic;
            List<ApiMethod> apiMethodList;
            MethodInfo methodInfo = apiMethod.MethodInfo;

            if (apiMethod.ApiMethodType == ApiMethodTypes.FixUrl)
            {
                if (!this._fixUrlApiMethodDic.TryGetValue(apiMethod.Method, out apiMethodDic))
                {
                    apiMethodDic = new Dictionary<string, ApiMethod>();
                    this._fixUrlApiMethodDic.Add(apiMethod.Method, apiMethodDic);
                }

                routUrl = apiMethod.RoutUrl;
                if (apiMethodDic.TryGetValue(routUrl, out oldApiMethod))
                {

                    throw new ArgumentException($"api方法{methodInfo.DeclaringType.FullName}.{methodInfo.Name}与{oldApiMethod.MethodInfo.DeclaringType.FullName}.{oldApiMethod.MethodInfo.Name}定义重复的路由:\"{routUrl}\"");
                }

                apiMethodDic.Add(routUrl, apiMethod);
            }
            else
            {
                if (!this._templateUrlApiMethodDic.TryGetValue(apiMethod.Method, out apiMethodList))
                {
                    apiMethodList = new List<ApiMethod>();
                    this._templateUrlApiMethodDic.Add(apiMethod.Method, apiMethodList);
                }

                apiMethodList.Add(apiMethod);
            }
        }

        private static string GetRoutTemplate(Type type)
        {
            string routTemplate = null;
            object[] objAtts = type.GetCustomAttributes(typeof(RouteAttribute), true);
            if (objAtts != null && objAtts.Length > 0)
            {
                routTemplate = ((RouteAttribute)objAtts[0]).Template;
            }

            if (string.IsNullOrWhiteSpace(routTemplate))
            {
                routTemplate = type.Name;
            }

            return routTemplate;
        }



        /// <summary>
        /// 尝试调用中间件处理[已处理返回true,否则返回false]
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response">http响应</param>
        /// <returns>已处理返回true,否则返回false</returns>
        protected override bool PrimitiveTryInvoke(HttpRequest request, out IHttpResponse response)
        {
            response = null;

            //固定url-api处理
            Dictionary<string, ApiMethod> methodDic;
            if ((request.QueryStringParams == null || request.QueryStringParams.Count == 0) &&
                this._fixUrlApiMethodDic.TryGetValue(request.Method, out methodDic))
            {
                //通过url直接找到处理方法后，直接调用
                if (methodDic.TryGetValue(request.Url, out ApiMethod apiMethod))
                {
                    response = apiMethod.Invoke(request);
                    if (response != null)
                    {
                        return true;
                    }
                }
            }

            //模板url-api处理
            if (this._templateUrlApiMethodDic.TryGetValue(request.Method, out List<ApiMethod> apiMethodList))
            {
                //通过url未接找到处理方法，则遍历相应http请求方法对应的处理字典集合，对每一项都尝试调用，直到调用成功为止                
                foreach (ApiMethod apiMethodInfo in apiMethodList)
                {
                    if (apiMethodInfo.TryInvoke(request, out response))
                    {
                        return true;
                    }
                }
            }


            return false;
        }



        /// <summary>
        /// 获取所有api列表
        /// </summary>
        /// <returns></returns>
        public string GetAllApiList()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("固定url-api\r\n");
            string splitLine = new string('=', 60);
            Type descriptionType = typeof(DescriptionAttribute);

            //[key:http-method;value:[key:url;value:APIMethodInfo]]
            foreach (KeyValuePair<string, Dictionary<string, ApiMethod>> kv in this._fixUrlApiMethodDic)
            {
                sb.AppendLine(splitLine);
                sb.Append("http请求类型:");
                sb.AppendLine(kv.Key);
                sb.AppendLine();

                foreach (KeyValuePair<string, ApiMethod> kv2 in kv.Value)
                {
                    this.AppendMethodInfo(sb, kv2.Value, descriptionType);
                }
            }

            sb.AppendLine(splitLine);
            sb.AppendLine(splitLine);
            sb.AppendLine("模板url-api\r\n");

            foreach (KeyValuePair<string, List<ApiMethod>> kv in this._templateUrlApiMethodDic)
            {
                sb.AppendLine(splitLine);
                sb.Append("http请求类型:");
                sb.AppendLine(kv.Key);
                sb.AppendLine();

                foreach (ApiMethod kv2 in kv.Value)
                {
                    this.AppendMethodInfo(sb, kv2, descriptionType);
                }
            }

            return sb.ToString();
        }


        private void AppendMethodInfo(StringBuilder sb, ApiMethod apiMethod, Type descriptionType)
        {
            sb.Append("方法名：");
            MethodInfo methodInfo = apiMethod.MethodInfo;
            sb.Append(methodInfo.ReturnType.Name);
            sb.Append(' ');
            sb.Append(methodInfo.Name);
            sb.AppendLine("(...)");

            sb.Append("说明：");
            object[] atts = methodInfo.GetCustomAttributes(descriptionType, true);
            if (atts != null && atts.Length > 0)
            {
                sb.AppendLine(((DescriptionAttribute)atts[0]).Description);
            }

            sb.Append("templateUrl：");
            sb.AppendLine(apiMethod.TemplateUrl);

            sb.Append("url：");
            sb.AppendLine(apiMethod.RoutUrl);

            this.AppendTemplatePara(sb, apiMethod.UrlTemplateApiTemplatePara, descriptionType);
            this.AppendTemplatePara(sb, apiMethod.QueryStringTemplateApiTemplatePara, descriptionType);

            sb.AppendLine("\r\n");
        }

        private void AppendTemplatePara(StringBuilder sb, ApiTemplatePara templatePara, Type descriptionType)
        {
            if (templatePara == null)
            {
                return;
            }

            object[] atts;
            ParameterInfo parameterInfo;

            foreach (UrlSegment urlSegment in templatePara.UrlSegments)
            {
                if (urlSegment.ApiParameterInfo == null)
                {
                    continue;
                }

                parameterInfo = urlSegment.ApiParameterInfo.ParameterInfo;
                if (parameterInfo == null)
                {
                    continue;
                }

                sb.Append("参数：");
                sb.Append(urlSegment.UrlParaName);
                sb.Append("     ");
                sb.Append(parameterInfo.ParameterType.FullName);


#if NET4_0
                if (parameterInfo.DefaultValue != null)
#else
                if (parameterInfo.HasDefaultValue)
#endif
                {
                    sb.Append("(默认值：");
                    sb.Append(parameterInfo.DefaultValue != null ? parameterInfo.DefaultValue.ToString() : "null");
                    sb.Append(")");
                }

                atts = parameterInfo.GetCustomAttributes(descriptionType, true);
                if (atts != null && atts.Length > 0)
                {
                    sb.Append("     ");
                    sb.Append(((DescriptionAttribute)atts[0]).Description);
                }

                sb.AppendLine();
            }
        }
    }
}
